package com.qingzhuge.common.utils;

import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.qingzhuge.common.Constants.DEFAULT_ENCODING;

/**
 * @author : zero.xiao
 * @description :
 * @modified :
 */
public final class HttpUtil {
    private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);

    private static final MediaType CONTENT_TYPE_FORM = MediaType.parse("application/x-www-form-urlencoded;CHARSET_UTF8=UTF-8");
    private static final int CONN_TIMEOUT = 10000;
    private static final int READ_TIMEOUT = 10000;

    private static final String PREFIX_HTTPS = "https";
    private static HttpClient client;

    static {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(128);
        cm.setDefaultMaxPerRoute(128);
        client = HttpClients.custom().setConnectionManager(cm).build();
    }

    private HttpUtil() {
    }


    public static String get(String url) {
        return get(url, DEFAULT_ENCODING, null, null, null);
    }

    public static String get(String url, String charset) {
        return get(url, charset, CONN_TIMEOUT, READ_TIMEOUT, null);
    }

    public static String get(String url, Map<String, String> p) {
        return get(url, DEFAULT_ENCODING, CONN_TIMEOUT, READ_TIMEOUT, p);
    }

    public static String get(String url, String charset, Map<String, String> p) {
        return get(url, charset, CONN_TIMEOUT, READ_TIMEOUT, p);
    }

    public static String post(String url, Map<String,String> params) {
        String pars = createLinkStringByMap(params);
        return post(url,pars);
    }

    public static String post(String url, String params) {
        RequestBody body = RequestBody.create(CONTENT_TYPE_FORM, params);
        Request request = new Request.Builder().url(url).post(body).build();
        return exec(request);
    }

    /**
     * 发送一个 Post 请求, 使用指定的字符集编码.
     *
     * @param url         请求地址
     * @param body        RequestBody
     * @param mimeType    例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
     * @param charset     编码
     * @param connTimeout 建立链接超时时间,毫秒.
     * @param readTimeout 响应超时时间,毫秒.
     * @return ResponseBody, 使用指定的字符集编码.
     */
    public static String post(String url, String body, String mimeType, String charset, Integer connTimeout, Integer readTimeout) {
        HttpClient client = null;
        HttpPost post = new HttpPost(url);
        String result = "";
        try {
            if (StringUtils.isNotBlank(body)) {
                HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
                post.setEntity(entity);
            }
            // 设置参数
            setRequestConfig(connTimeout, readTimeout, post);
            HttpResponse res;
            if (url.startsWith(PREFIX_HTTPS)) {
                // 执行 Https 请求.
                client = createSSLInsecureClient();
                res = client.execute(post);
            } else {
                // 执行 Http 请求.
                client = HttpUtil.client;
                res = client.execute(post);
            }
            result = EntityUtils.toString(res.getEntity(), charset);
        } catch (GeneralSecurityException | IOException e) {
            e.printStackTrace();
        } finally {
            post.releaseConnection();
            if (url.startsWith(PREFIX_HTTPS) && client instanceof CloseableHttpClient) {
                try {
                    ((CloseableHttpClient) client).close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }


    /**
     * 发送一个 GET 请求
     *
     * @param connTimeout 建立链接超时时间,毫秒.
     * @param readTimeout 响应超时时间,毫秒.
     * @param param       参数
     */
    public static String get(String url, String charset, Integer connTimeout, Integer readTimeout, Map<String, String> param) {
        String result = null;
        try {
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();
            HttpClient client = null;
            HttpGet get = new HttpGet(uri);
            HttpResponse response = null;
            try {
                setRequestConfig(connTimeout, readTimeout, get);
                if (url.startsWith(PREFIX_HTTPS)) {
                    // 执行 Https 请求.
                    try {
                        client = createSSLInsecureClient();
                        response = client.execute(get);
                    } catch (GeneralSecurityException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 执行 Http 请求.
                    client = HttpUtil.client;
                    response = client.execute(get);
                }
                assert response != null;
                result = EntityUtils.toString(response.getEntity(), charset);
            } finally {
                get.releaseConnection();
                if (response instanceof CloseableHttpResponse) {
                    ((CloseableHttpResponse) response).close();
                }
                if (client instanceof CloseableHttpClient) {
                    ((CloseableHttpClient) client).close();

                }
            }
        } catch (URISyntaxException | IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private static void setRequestConfig(Integer connTimeout, Integer readTimeout, HttpRequestBase requestBase) {
        // 设置参数
        RequestConfig.Builder customReqConf = RequestConfig.custom();
        if (connTimeout != null) {
            customReqConf.setConnectTimeout(connTimeout);
        }
        if (readTimeout != null) {
            customReqConf.setSocketTimeout(readTimeout);
        }

        requestBase.setConfig(customReqConf.build());
    }

    private static String exec(okhttp3.Request request) {
        OkHttpClient okHttpClient = getOkHttpClient();
        long millis = System.currentTimeMillis();
        try {
            return execResult(request, okHttpClient);
        } catch (IOException e) {
            long currentTimeMillis = System.currentTimeMillis();
            long timeout = currentTimeMillis - millis;
            logger.error("服务器请求异常，设置超时时间：{}，重新连接 \n {}", timeout, e);
            okHttpClient = getOkHttpClient(timeout, timeout, timeout);
            try {
                return execResult(request, okHttpClient);
            } catch (IOException ex) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private static String execResult(Request request, OkHttpClient okHttpClient) throws IOException {
        Call call = okHttpClient.newCall(request);
        Response response = call.execute();
        if (!response.isSuccessful()) {
            throw new RuntimeException("Unexpected code " + response);
        }
        return Objects.requireNonNull(response.body()).string();
    }

    private static OkHttpClient getOkHttpClient() {
        return new OkHttpClient();
    }

    /**
     * 设置有超时时间的 OkHttpClient
     *
     * @param connectTimeout 连接超时时间
     * @param readTimeout    读取超时时间
     * @param writeTimeout   发送超时时间
     * @return OkHttpClient
     */
    private static OkHttpClient getOkHttpClient(Long connectTimeout,
                                                Long readTimeout,
                                                Long writeTimeout) {
        OkHttpClient httpClient = getOkHttpClient();
        httpClient.newBuilder().connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS);
        return httpClient;
    }

    /**
     * 创建 SSL连接
     */
    private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true).build();
        // 指定TLS版本
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        // 设置httpclient的SSLSocketFactory
        return HttpClients.custom().setSSLSocketFactory(sslsf).build();
    }

    /**
     * 参数字符串拼接
     */
    private static String createLinkStringByMap(Map<String, String> params) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (i == keys.size() - 1) {
                //拼接时，不包括最后一个&字符
                sb.append(key).append("=").append(value);
            } else {
                sb.append(key).append("=").append(value).append("&");
            }
        }
        return sb.toString();
    }
}
